﻿// <copyright file="ArrayExtensions.cs" company="DotNetLib">
//
// .netlib: http://www.codeplex.com/DotNetLib
//
// This code is released under the New BSD License.
// A copy of this license is available at:
// http://www.codeplex.com/DotNetLib/license
//
// </copyright>

namespace DotNetLib.Xml
{
  using System;
  using System.Xml.Serialization;

  public class AbstractXmlSerializer<T> : IXmlSerializable
  {
    // Override the Implicit Conversions Since the XmlSerializer
    // Casts to/from the required types implicitly.
    public static implicit operator T(AbstractXmlSerializer<T> o)
    {
      if (o == null)
      {
        throw new ArgumentNullException("o");
      }

      return o.Data;
    }

    public static implicit operator AbstractXmlSerializer<T>(T o)
    {
      return o == null ? null : new AbstractXmlSerializer<T>(o);
    }

    private T data;
    /// <summary>
    /// [Concrete] Data to be stored/is stored as XML.
    /// </summary>
    public T Data
    {
      get { return this.data; }
      set { this.data = value; }
    }

    /// <summary>
    /// DO NOT USE! This is only added to enable XML Serialization.
    /// </summary>
    /// <remarks>DO NOT USE THIS CONSTRUCTOR</remarks>
    public AbstractXmlSerializer()
    {
    }

    /// <summary>
    /// Initialises the Serializer to work with the given data.
    /// </summary>
    /// <param name="data">Concrete Object of the AbstractType Specified.</param>
    public AbstractXmlSerializer(T data)
    {
      this.data = data;
    }

    #region IXmlSerializable Members

    public System.Xml.Schema.XmlSchema GetSchema()
    {
      return null; // this is fine as schema is unknown.
    }

    public void ReadXml(System.Xml.XmlReader reader)
    {
      if (reader == null)
      {
        throw new ArgumentNullException("reader");
      }

      // Cast the Data back from the Abstract Type.
      string typeAttrib = reader.GetAttribute("type");

      // Ensure the Type was Specified
      if (typeAttrib == null)
        throw new ArgumentNullException("Unable to Read Xml Data for Abstract Type '" + typeof(T).Name +
            "' because no 'type' attribute was specified in the XML.");

      Type type = Type.GetType(typeAttrib);

      // Check the Type is Found.
      if (type == null)
        throw new InvalidCastException("Unable to Read Xml Data for Abstract Type '" + typeof(T).Name +
            "' because the type specified in the XML was not found.");

      // Check the Type is a Subclass of the AbstractType.
      if (!type.IsSubclassOf(typeof(T)))
        throw new InvalidCastException("Unable to Read Xml Data for Abstract Type '" + typeof(T).Name +
            "' because the Type specified in the XML differs ('" + type.Name + "').");

      // Read the Data, Deserializing based on the (now known) concrete type.
      reader.ReadStartElement();
      this.Data = (T)new
          XmlSerializer(type).Deserialize(reader);
      reader.ReadEndElement();
    }

    public void WriteXml(System.Xml.XmlWriter writer)
    {
      if (writer == null)
      {
        throw new ArgumentNullException("writer");
      }

      // Write the Type Name to the XML Element as an Attrib and Serialize
      Type type = this.data.GetType();

      // BugFix: Assembly must be FQN since Types can/are external to current.
      writer.WriteAttributeString("type", type.AssemblyQualifiedName);
      new XmlSerializer(type).Serialize(writer, this.data);
    }

    #endregion
  }
}
